let socket, roomId, me;
// let $chessboard, $result, $initializer, $exit;
let $initializer, $area, $chessboard, $controller, $exit;
let rows = 0, cols = 0, chessboard = [];

$(function () {
  // 初始化全局公开元素
  $initializer = $('div.initializer'); // 游戏初始化界面
  $area = $('div.core-area'); // 游戏视口区
  $chessboard = $area.find('div.chessboard'); // 棋盘
  $exit = $area.find('button.btn[name="exit"]'); // 退出游戏

  // 读取 URL 路径参数
  const url = new URLSearchParams(window.location.search);
  roomId = url.get('room');
  me = url.get('me');

  // 根据是否传递 URL 参数来决定是 <创建游戏> 还是 <加入游戏>？
  if ($.isEmptyObject(roomId) || $.isEmptyObject(me)) {
    $area.addClass('hidden');
    $initializer.removeClass('hidden');
    bindInitializerEvent();
  } else {
    connectWebSocketServer();
  }
});

/**
 * 绑定按钮控制器的点击事件
 */
function bindInitializerEvent() {
  // 创建游戏，此功能用 <HTTP> 请求协议发送。
  // 当服务端游戏房间创建完成后会返回房间的一些信息，然后在 <success> 中显示房间的分享链接，然后就可以开始游戏啦~
  $initializer.on('click', 'button.btn[name="create"]', function (e) {
    $.ajax("http://localhost:8888/room/create", {
      type: 'POST',
      dataType: 'json',
      data: $('form').serialize(),
      success: function (resp) {
        if (resp.code === 200) {
          showMessage("创建棋盘成功!");
          console.log('创建棋盘成功：');
          console.log(resp.data);

          // 提取响应参数内容
          const data = resp.data;
          me = data.me;
          roomId = data.roomId;

          const $create = $initializer.find('button.btn[name="create"]');
          const $start = $initializer.find('button.btn[name="start"]');
          const $content = $initializer.find('div.create-result');
          const $createInfo = $content.find('div.result-link');

          $create.prop('disabled', true);
          $start.prop('disabled', false);
          $createInfo.text(`http://localhost:8888?room=${roomId}&me=${data.opponent}`);

          $initializer.addClass('finished');
        } else {
          showMessage(resp.message, "error");
        }
      }
    });
  });

  // 开始游戏
  $initializer.on('click', 'button.btn[name="start"]', function (e) {
    if (!$.isEmptyObject(roomId) && confirm("是否已将链接分享给对方？")) {
      let url = new URL(window.location.href);
      let params = new URLSearchParams(url.search);
      params.set('room', roomId);
      params.set('me', me);
      url.search = params.toString();
      window.location.assign(url.href);
    }
  });
}

/**
 * 退出游戏
 */
function bindExitEvent() {
  $exit.removeClass('hidden').bind('click', function (e) {
    socket && socket.send(JSON.stringify({type: 'exit'}));
  });
}

/**
 * 连接 <WebSocket> 服务器
 */
function connectWebSocketServer() {
  $initializer.addClass('hidden');
  $area.removeClass('hidden');

  // 创建 WebSocket 链接
  socket = new ReconnectingWebSocket(`ws://localhost:8888/room/${roomId}/${me}`, null, {
    reconnectInterval: 5000,
  });

  // 建立连接事件
  socket.onopen = () => {
    console.log("连接成功");
    socket.send(JSON.stringify({type: 'ping'}));
  };

  // 处理服务端的消息
  socket.onmessage = (e) => {
    const result = JSON.parse(e.data);
    if (handlers.hasOwnProperty(result.type)) {
      handlers[result.type](socket, result);
    } else {
      handlers.other(socket, result);
    }
  };

  // 关闭连接
  socket.onclose = () => console.log("连接关闭");
}

/**
 * 创建一个棋盘
 *
 * @param {[][]} chessboard 棋盘原始数据
 * @param {number} row 落子的 X 坐标
 * @param {number} column 落子的 Y 坐标
 */
function createChessboard(chessboard, row, column) {
  $chessboard.empty().css('--row', row);
  for (let x = 0; x < row; x++) {
    let $row = $('<div class="chess-row"/>');
    for (let y = 0; y < column; y++) {
      let $item = $('<div class="chess-piece"/>').data({"x": x, "y": y});
      let item = chessboard[x][y];
      item.target = $item;
      if (item.role !== 'NONE') {
        $item.addClass(item.role.toLowerCase());
      }
      $row.append($item);
    }
    $chessboard.append($row);
  }
}

/**
 * 刷新棋盘，当无论哪个用户落子后都需要刷新棋盘信息，在前端页面用户是无法知道自己是执 <黑子> 还是 <白子> 的，
 * 因此，需要由后端决定该怎么走，完了之后由后端将最新的棋盘数据推送回来，前端完成数据渲染。
 *
 * @param {[][]} original 棋盘原始数据
 * @param {[][]} latest 落子后的棋盘最新数据
 * @param {number} row 落子的 X 坐标
 * @param {number} column 落子的 Y 坐标
 */
function refreshChessboard(original, latest, row, column) {
  for (let x = 0; x < row; x++) {
    for (let y = 0; y < column; y++) {
      let item = original[x][y];
      let current = latest[x][y];
      item.role = current.role;
      if (current.role !== 'NONE') {
        item.target.addClass(current.role.toLowerCase());
      } else {
        item.target.removeClass('white').removeClass('black');
      }
    }
  }
}

/**
 * 绑定 <棋子> 的落子点击事件
 */
function bindChessPieceEvent() {
  $chessboard.on('click', 'div.chess-piece', function (e) {
    let $item = $(this);
    if ($chessboard.hasClass('disabled')) {
      showMessage("请等待对方走棋！", 'warning');
    } else {
      doMove($item);
    }
    stopPropagation(e);
  })
}

/**
 * 执行 <落子> 动作，此操作需要获取棋盘的落子坐标，并将坐标推送给服务器，服务器会通过 <WebSocket> 将最新数据返回回来，
 * 然后在 <refresh> 中刷新棋盘数据。
 *
 * @param {jQuery} $item 点击的棋盘元素 jQuery 实例
 */
function doMove($item) {
  let {x, y} = $item.data();
  let item = chessboard[x][y];
  if (item.role === 'NONE') {
    $chessboard.addClass('disabled');
    socket.send(JSON.stringify({type: 'move', payload: [x, y]}));
  } else {
    showMessage('此区域已有棋子，不可落在此处！', 'warning');
  }
}

/**
 * 显示一个简单的 <Toast> 消息提醒
 *
 * @param {String} message 提醒消息
 * @param {String} type 消息类型，可用类型有：success | info | warning | error；
 */
function showMessage(message, type) {
  $.toast({
    text: message,
    // heading: "信息提示",
    position: 'top-center',
    loader: false,
    icon: type || "success",
  });
}

/**
 * 阻止事件冒泡
 *
 * @param {Event} e 点击事件
 */
function stopPropagation(e) {
  let event = e || window.event;
  if (e.preventDefault) {
    e.preventDefault();
  } else {
    event.returnValue = false;
  }
}

// WebSocket 消息通讯中的不同消息处理器
const handlers = {

  // 用于检测在建立链接初期是否正常，正常的话，服务器会返回一个 <pong>。
  // 然后马上发起玩家注册请求，如果请求正常，则服务端会返回一个 <init> 类型的数据，用于初始化棋盘。
  ping: (socket, result) => {
    showMessage("已连接至服务器！");
    socket.send(JSON.stringify({type: 'register'}));
  },

  // 游戏初始化
  // 服务端会返回棋盘的一些基础信息
  // 包括是否冻结棋盘（我先走还是对方先走之类的）和棋盘的一些初始化数据。
  init: (socket, result) => {
    const data = result.data;
    console.log(data);
    const over = data.over;
    chessboard = data.chessboard;
    createChessboard(chessboard, data.rows, data.cols);
    bindExitEvent();
    if (over) {
      $chessboard.attr('result', data.result);
      $initializer.addClass('hidden');
      return;
    }
    if (data.frozen) {
      $chessboard.addClass('disabled');
    }
    bindChessPieceEvent();
  },

  // 玩家上线的消息提示
  online: (socket, result) => {
    showMessage(result.data, "info");
  },

  // 玩家掉线的消息提示
  offline: (socket, result) => {
    showMessage(result.data, "warning");
  },

  // 无论是当前玩家还是对方玩家在点击棋盘之后都会把最新的棋盘信息推送过来
  // 然后更新棋盘的棋子数据，这是为了防止用户胡乱修改游戏数据！
  refresh: (socket, result) => {
    const data = result.data;
    refreshChessboard(chessboard, data.chessboard, data.rows, data.cols);
  },

  // 用户落子之后，棋盘会冻结无法继续点击，
  // 因此必须等对方玩家落子后我方才能继续点击。
  unfreeze: (socket, result) => {
    $chessboard.removeClass('disabled');
  },

  // 服务端的 Socket 检测到异常，房间解散或者不存在之类的~
  error: (socket, result) => {
    showMessage(result.data, "error");
    socket.close();
    // 回到首页，丢弃 URL 中的房间参数
    let params = new URLSearchParams(window.location.href);
    params.delete('room');
    params.delete('me');
    window.location.assign(decodeURIComponent(params.toString()));
  },

  // 退出游戏，通常此操作由用户主动发起；
  exit: (socket, result) => {
    showMessage(result.data, "info");
    socket.close();
    let url = new URL(window.location.href);
    let params = new URLSearchParams(url.search);
    params.delete('room');
    params.delete('me');
    url.search = params.toString();
    console.log(url.href);
    window.location.assign(url.href);
  },

  // 游戏其中一方胜利，游戏结束。
  finished: (socket, result) => {
    $chessboard.attr('result', result.data);
    $initializer.addClass('hidden');
  },

  // 其他位置的消息都会转发到此处来，简单给个提示就行了。
  other: (socket, result) => showMessage(result.data, "info"),
};